package com.itbaizhan.myshiro.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.itbaizhan.myshiro.listener.MySessionListener;
import com.itbaizhan.myshiro.realm.MyRealm;
import com.itbaizhan.myshiro.realm.MyRealm2;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.*;

//securityManager与JdbcRealm放进spring容器中进行管理
@Configuration
public class ShiroConfig {
    // Realm管理者
    @Bean
    public ModularRealmAuthenticator modularRealmAuthenticator(){
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        //设置认证策略
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }
//SecurityManager
    @Bean
    public DefaultWebSecurityManager securityManager(MyRealm myRealm,
                                                     MyRealm2 myRealm2,
                                                     SessionManager sessionManager,
                                                     CookieRememberMeManager cookieRememberMeManager,
                                                     EhCacheManager ehCacheManager){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置Realm管理者 (上面的认证策略)，（需要在设置realm前）
        securityManager.setAuthenticator(modularRealmAuthenticator());

        //2,为securityManager对象设置Realm
        //securityManager.setRealm(myRealm);
        //有多个realm要放进securityManager时用集合类型
        List<Realm> realms = new ArrayList<>();
        realms.add(myRealm);
        //测试针对抛出的异常要注释，不可以有两个表，因为无法判断
        // 查询的是哪个表，所以不会抛出异常，实际应用中只有用户表用来登录
        //角色表不是用来登录，所以添加一个用户表就可以
        //realms.add(myRealm2);
        //注意setRealms 不是setRealm
        securityManager.setRealms(realms);
        //把会话管理放进securityManager
        securityManager.setSessionManager(sessionManager);
        //3,把CookieRememberManager 放进securityManager
        securityManager.setRememberMeManager(cookieRememberMeManager);
        //设置缓存管道  用于授权的缓存
        securityManager.setCacheManager(ehCacheManager);
        return securityManager;
    }

//    配置加密算法
    @Bean
    public HashedCredentialsMatcher matcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//配置加密算法
        //加密次数
        hashedCredentialsMatcher.setHashIterations(5);
        return hashedCredentialsMatcher;
    }

    /*@Bean
    public JdbcRealm jdbcRealm(DataSource dataSource){
        JdbcRealm jdbcRealm = new JdbcRealm();
        //1,把数据源放进jdbcRealm
        jdbcRealm.setDataSource(dataSource);
        return jdbcRealm;
    }*/
    //使用自定义realm
    @Bean
    public MyRealm myRelam(HashedCredentialsMatcher hashedCredentialsMatcher){
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return myRealm;
    }
    @Bean
    public MyRealm2 myRealm2(HashedCredentialsMatcher hashedCredentialsMatcher){
        MyRealm2 myRealm2 = new MyRealm2();
        myRealm2.setCredentialsMatcher(hashedCredentialsMatcher);
        return myRealm2;
    }

//    配置过虑器    Shiro内置了很多过滤器：
        @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
       //1创建过虑器工厂
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
       //2过虑器工厂设置SecurityManager
         factoryBean.setSecurityManager(securityManager);
        //3设置shiro拦截规则
        //map集合
        Map<String,String> filterMap =  new HashMap<>();
        //      不拦截的资源
        filterMap.put("/login.html","anon");
        filterMap.put("/fail.html","anon");
        filterMap.put("/user/login","anon");
        filterMap.put("/css/**","anon");
        //配置鉴权过滤器
        //鉴权过滤器要写在 /**  之前，否则认证都无法通过
        filterMap.put("/reportform/find","perms[/reportform/find]");
        filterMap.put("/salary/find","perms[/salary/find]");
        filterMap.put("/staff/find","perms[/staff/find]");


        //其余的都 拦截
        //filterMap.put("/**","authc");
        //配置了rememberMe，不可以上页面的 filterMap.put("/**","authc");会把remember也拦截
         //auth过虑器表示需要认证才能进行访问，user过滤器表示配置记住我或者认证都可以访问
         filterMap.put("/user/pay","authc"); //支付
         //filterMap.put("/**","user");  //测试shiro注解时注释，否则都会被拦截

        //4将拦截规则设置给过虑器工厂
       factoryBean.setFilterChainDefinitionMap(filterMap);
        //5配置登录页面
        factoryBean.setLoginUrl("/login.html");
        //配置权限不足的页面  访问时权限不足跳转的页面 ，不是在controller中配置
        factoryBean.setUnauthorizedUrl("/noPermission.html");
        return factoryBean;

    }
    //2,会话管理器  参数是自己定义的会话监听器
    @Bean
    public SessionManager sessionManager(MySessionListener mySessionListener){
        //创建会话管理
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //创建会话监听集合
        List<SessionListener> sessionListeners = new ArrayList<>();
        //将自定义的监听器放进集合中
        sessionListeners.add(mySessionListener);
        //将监听集合设置到会话管理中
        sessionManager.setSessionListeners(sessionListeners);
        //全局会话超时时间  单位毫秒  这里是5秒
        //sessionManager.setGlobalSessionTimeout(5000);
        //是否删除无效的session对象，默认为true
        sessionManager.setDeleteInvalidSessions(true);
        //是否开启定时调度器检测过期session,默认为true
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;


    }
    //remember me 的步骤：1,Cookie生成器
    // 2,记住我管理 remember me
    // 3,把CookieRememberManager 放进securityManager  4,在实体类实现 Serializable
    //1,Cookie生成器
    @Bean
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        //设置cookie有效时间20秒
        simpleCookie.setMaxAge(20);
        return simpleCookie;
    }
    //2,记住我管理 remember me
    @Bean
    public CookieRememberMeManager cookieRememberMeManager(SimpleCookie simpleCookie){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(simpleCookie);
        //Cookie加密  6ZmI6I2j3Y+R1aSn5BOlAA== 这个不可以乱写
        cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));
        return cookieRememberMeManager;
    }


    //开始shiro注解
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    //开启AOP注解支持  因为开启shiro注解必须要开启这，shiro注解的底层是AOP
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
//  2, 注册ShiroDialect  用于在thymeleaf中使用shiro标签，控前端的显示内容

    /*1,在pom中引入Shiro和Thymeleaf的整合依赖
    <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>*/
    //3,在Thymeleaf中使用Shiro标签，控制前端的显示内容
    /*<!DOCTYPE html>
  //添加thymeleaf-shiro标签
<html xmlns:th="http://www.thymeleaf.org"
    xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
  <meta charset="UTF-8">
  <title>主页面</title>
</head>
<body>
<h1>主页面</h1>
<ul>
  <li shiro:hasPermission="/reportform/find"><a href="/reportform/find">查询报表</a></li>
  <li shiro:hasPermission="/salary/find"><a href="/salary/find">查询工资</a></li>
  <li shiro:hasPermission="/staff/find"><a href="/staff/find">查询员工</a></li>
</ul>
<h2><a href="/user/logout">退出登录</a></h2>
</body>
</html>

     */
    @Bean
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }

    //创建CacheManager  授权缓存管理
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        ehCacheManager.setCacheManagerConfigFile("classpath:shiro-ehcache.xml");
        return ehCacheManager;
    }
}

/*Shiro内置了很多过滤器：
* 过滤器	        过滤器类	                说明
anon	AnonymousFilter	            配置不需要登录即可访问的资源
authc	FormAuthenticationFilter	配置登录认证后才可以访问的资源
user	UserFilter	                配置登录认证或“记住我”认证后才可以访问的资源*/




/*如果有多个Realm，怎样才能认证成功，这就是认证策略。认证策略主要使用的是 AuthenticationStrategy接口，这个接口有三个实现类：
* 策略	                                意义
AtLeastOneSuccessfulStrategy（默认）	只要有一个Realm验证成功即可，返回所有成功的认证信息
FirstSuccessfulStrategy	            只要有一个Realm验证成功即可，只返回第一个成功的认证信息，其他的忽略
AllSuccessfulStrategy	        所有Realm验证成功才算成功，如果有一个失败则认证失败
*
* 在ModularRealmAuthenticator中的doMultiRealmAuthentication方法中的 return aggregate;添加断点可以查看认证通过信息
* */

/*加密算法
*1 修改数据库和实体类，添加盐字段，并修改数据库用户密码为加盐加密后的数据。
*2 修改自定义Realm

@Component
public class MyRealm extends AuthorizingRealm {
  @Autowired
  private UserInfoMapper userInfoMapper;

  // 自定义认证方法
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    // 1.获取用户输入的用户名
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    String username = token.getUsername();

    // 2.根据用户名查询用户
    QueryWrapper<Users> wrapper = new QueryWrapper<Users>().eq("username", username);
    Users users = usersMapper.selectOne(wrapper);

    // 3.将查询到的用户封装为认证信息
    if (users == null) {
      throw new UnknownAccountException("账户不存在");
     }


     * 参数1：用户
     * 参数2：密码
     * 参数3：盐  加密的干拢数据
     * 参数4：Realm名

    return new SimpleAuthenticationInfo(users,
            users.getPassword(),
            ByteSource.Util.bytes(users.getSalt()),
            "myRealm");
            }

// 自定义授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
        }
        }

3，在注册自定义Realm时添加加密算法

// 配置加密算法
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //加密算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //加密的次数
        hashedCredentialsMatcher.setHashIterations(5);
        return hashedCredentialsMatcher;
        }

// Realm
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
        MyRealm myRealm = new MyRealm();
        // 设置加密算法
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return myRealm;
        }
        * */